 /*!@defgroup client Cliente
 * @brief Módulo Cliente
 */

/** @{ */
 
 /*!@file client.c
 *@brief Go Back N - Parte do Cliente
 *@author Igor Coelho
 *@author Thiago Stuckert
 *@date 09  de Maio
 *@version 0.1 
 */

/*Para compilar digite: gcc -o client client.c -lm -g */
/*Para rodar digite: ./client 127.0.0.1 */

#include <stdbool.h> 
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/time.h>
#include <signal.h>
#include <math.h>
/*Tamanho de cada pacote, não deve ultrapassar 512 bytes*/
/*The length field of the UDP header allows up to 65535 bytes of data.
However, if you are sending your UDP packet across an Ethernet network, the
Ethernet MTU is 1500 bytes, limiting the maximum datagram size. Also, some
routers will attempt to fragment large UDP packets into 512 byte chunks.*/

/*Tamanho do buffer*/
#define buffer_size 1024
/*Para evitar que divida em mais de um*/
#define packet_size 64
#define PORT 5687
/*Tamanho da janela*/
#define N 10
/*Funcao chamada apertando CTRL+\, sai do programa e exibe dados finais.*/
/*Constante utilizada para o calculo do EstimatedRTT*/
#define alfa 0.125
/*Constante utilizada para o calculo do DevRTT*/        
#define beta 0.25

#define debug printf
void sair(){
    printf("\nSaida chamada.\n");
    exit(0);
}

int main(int argc, char *argv[]) {
    /*Heheheh essa lista de variaveis ta bizarra melhor da uma comentada nela!*/
	int sock, aux, i;
	struct sockaddr_in addr;
	unsigned int size = sizeof addr;
	char packet[packet_size];
        char *checksum="checksum";
        char *data="data";
	struct timeval tempo, tempo2;
        double SampleRTT;
        int base = 0;
        int nextseqnum=1;
        int TimeoutInterval;
        int EstimatedRTT;
        int DevRTT;
        bool timeout;
        bool wrong_ack;
        int seqnumreceived;
        int ACK_received;
        char *checksum_received;
        int count_packet_received;
        char **buffer;
        int qtde_pacotes;
        
        /*Eu fiz uma coisa errada na hora de considerar a quantidade de pacotes*/
        /*Os pacotes nao contém apenas dados contém sequence number e checksum*/
        /*Portanto nao posso definir quantidade de pacotes como (buffer_size/packet_size)*/
        /*A definição correta seria algo do tipo ((buffer_size)/(packet_size-tamanho do sequence number e do checksum ))*/
        
        qtde_pacotes=(buffer_size/packet_size);
        
        /*Aloco memória para o buffer*/
        buffer =(char **) calloc((qtde_pacotes),sizeof(char*));
        if(buffer==NULL){
            perror("ERRO: Memoria insuficiente!\n");   
        }
    
        for(i=0;i<qtde_pacotes;i++){
            buffer[i]= (char *) calloc(packet_size,sizeof(char) );
            if(buffer[i]==NULL){
                perror("ERRO: Memoria insuficiente!\n");   
            }
        }
        /*Neste caso estou escrevendo o dado porém se sobrar tempo poderíamos ler de um arquivo*/
        for(i=0;i<qtde_pacotes;i++)
            sprintf(buffer[i],"%s %d","Dado do pacote ",i);
        
        
	/*Prepara o sinal de saida.*/
	signal(SIGQUIT,sair);

	/* Checa se recebeu o IP nos parâmetros. */
	if (argc < 2) {
		perror("Falta argumento: endereço de IP do servidor.");
		return EXIT_FAILURE;
	}

	/* Cria o socket. */
	sock = socket(PF_INET, SOCK_DGRAM, 0);
	if (sock == -1) {
		perror("Não conseguiu criar o socket.");
		return EXIT_FAILURE;
	}

	/* Cria o endereço do servidor onde vai conectar. */
	addr.sin_family = AF_INET;
	addr.sin_port = htons(PORT);
	addr.sin_addr.s_addr = inet_addr(argv[1]);
	memset(&addr.sin_zero, 0, 8);

        //Enquanto nao enviei o buffer todo

        //envio o primeiro pacote da janela e inicializo o timer para ele
        i=0;
        sprintf(packet,"%d %s %s",i,buffer[i],checksum);
      
	/*Envia packet ao servidor*/
	aux = sendto(sock, packet, packet_size, 0, (struct sockaddr *) &addr, size);
	/*Inicializo o timer para o pacote*/
        
        if (aux < 0) {
	   perror("Erro ao enviar mensagem");
	   return EXIT_FAILURE;
	}
	/*Ve o tempo que foi enviado*/
	aux = gettimeofday(&tempo,NULL);
	if (aux < 0) {
		perror("Erro ao pegar tempo de envio");
		return EXIT_FAILURE;
	}
		
	/* Envia todos os pacotes da janela */
	for (i = 1; i<N; i++) {
                
                sprintf(packet,"%d %s %s",i,buffer[i],checksum);
      
		/*Envia packet ao servidor*/
		aux = sendto(sock, packet, packet_size, 0, (struct sockaddr *) &addr, size);
		
                if (aux < 0) {
			perror("Erro ao enviar mensagem");
			return EXIT_FAILURE;
		}
		/*Ve o tempo que foi enviado*/
		aux = gettimeofday(&tempo,NULL);
		if (aux < 0) {
			perror("Erro ao pegar tempo de envio");
			return EXIT_FAILURE;
		}
	}
        

        DevRTT=0;
        
        /*Recebo todos os acks ou estora o timeout ou recebo um ack fora da ordem*/
        wrong_ack=false;
        count_packet_received=0;
        /*Devo ficar nesse loop enquanto nao estoro o timeout ou recebo um ack errado*/
        while((!wrong_ack)&&(count_packet_received<N)){
            /*Recebo resposta do pacote*/
	   aux = recvfrom(sock, packet, packet_size, 0, (struct sockaddr *) &addr, &size);
	   if (aux < 0) {
	       perror("Erro ao receber mensagem");
	       return EXIT_FAILURE;
	   }
           
           seqnumreceived = atoi(strtok (packet," "));
           ACK_received = atoi(strtok (NULL, " "));
           checksum_received = strtok (NULL, " ");
           printf("Packet received %d %d %s\n",seqnumreceived,ACK_received,checksum_received);
           if(ACK_received==base){
               /*Eu ando com a base*/
               base++;
               /*Utilizo esse contador de forma que se eu receber todos os pacotes da
               janela antes do timeout do primeiro estourar eu paro de esperar*/
                count_packet_received++;
                /*Calculo do SampleRTT*/
                
                /*Ve o tempo em que foi recebida resposta*/
	       aux = gettimeofday(&tempo2,NULL);
	       if (aux < 0) {
		  perror("Erro ao pegar tempo de retorno");
	           return EXIT_FAILURE;
	       }

        	/*Calcula o tempo do ping em milisegundos*/
	       SampleRTT = (double) ((((tempo2.tv_sec*1000000)+(tempo2.tv_usec)) -                ((tempo.tv_sec*1000000)+(tempo.tv_usec)))/1000.0);
        
	       /*Imprime o SampleRTT*/
	       debug("SampleRTT da mensagem %d igual a %.3fms!\n", ACK_received, SampleRTT);
               /*EstimatedRTT deve ser igual ao primeiro SampleRTT, porém lembre-se que o pacote
               nao pode ter sido perdido*/
               if(base==0){
                    EstimatedRTT=SampleRTT;
               }else{
                    EstimatedRTT = (1-alfa) * EstimatedRTT + alfa * SampleRTT;
               }
               DevRTT = (1- beta) * DevRTT + beta * abs(SampleRTT - EstimatedRTT);
               TimeoutInterval = EstimatedRTT + 4 * DevRTT;
               debug("EstimatedRTT = %d\tDevRTT = %d\tTimeoutInterval = %d\n",EstimatedRTT,DevRTT,TimeoutInterval);
           }else{
               /*Recebi um ack fora de ordem*/
               printf("Recebi um ack fora da ordem\n");
               debug("Deveria receber %d mas recebi %d\n",base,ACK_received);
               wrong_ack=true;    
           }
        }
        
        /*Desaloco o buffer*/
       for(i=0;i<(buffer_size/packet_size);i++)
            free(buffer[i]);
     
        free(buffer);
        
	close(sock);

	return 0;
}

/*
TimeoutInterval = EstimatedRTT + 4 * DevRTT
EstimatedRTT = (1-alfa) * EstimatedRTT + alfa * SampleRTT
O primeiro Estimated RTT acredito que será igual ao SampleRTT
alfa tem valor tipico de 0.125 .
DevRTT = (1- beta) * DevRTT + beta * | SampleRTT - EstimatedRTT |
O primeiro DevRTT deve ser igual a 0
beta tem valor tipico 0.25 .

*/

/*
N = windowsize
Rn = request number
Sn = sequence number
Sb = sequence base
Sm = sequence max
   Receiver:
   Rn = 0
   Do the following forever:
   If the packet received = Rn the packet is error free
Accept the packet and send it to a higher layer
Rn = Rn +1
Send a Request for Rn
Else
Refuse packet
Send a Request for Rn
   Sender:
   Sb = 0
Sm = N – 1
   Repeat the following steps forever:
   1. If you receive a request number where Rn > Sb
Sm = Sm + (Rn – Sb)
Sb =Rn
   2. If no packet is in transmission,
Transmit a packet where Sb <= Sn <= Sm.
Packets are transmitted in order.
*/
